package misc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.semanticweb.owlapi.model.OWLProperty;

public class Refactor {

	// @formatter:off
	/**
	 * This should be called like this:
	 * 
	 * Refactor fromOWLFileName toOWLFileName urlListFileName copy append
	 * 
	 * The "copy" option is optional and it will copy axioms without deleting them
	 * from the source files. Otherwise, the axioms are deleted. The "append"
	 * option is optional and it will add the axioms to an existing file (which
	 * must be present). Otherwise, a new file is created.
	 * 
	 * The IRI list file is in the following form: 
	 * http://some/uri 							(to only work on the specified uri) 
	 * http://some/uri :subs 				(to only work on the sub uris)
	 * http://some/uri :with-subs 	(to work on the uri and the sub uris)
	 * 
	 * The script will work on classes or properties based on the type of the IRI.
	 * 
	 * The script will also create a "referencing-" output file that should contain
	 * other referencing axioms, if any, either copied or deleted.
	 * 
	 * @param args
	 * @throws OWLOntologyCreationException
	 * @throws IOException
	 * @throws OWLOntologyStorageException
	 */
	// @formatter:on

	public static void main(String[] args) throws OWLOntologyCreationException, IOException,
			OWLOntologyStorageException {
		List<String> commands = Arrays.asList(args).subList(3, args.length);
		new Refactor().run(args[0], args[1], args[2], commands);
	}

	Set<String> entitiesOnly = new HashSet<String>();
	Set<String> subsOnly = new HashSet<String>();
	Set<String> entityWithSubs = new HashSet<String>();

	OWLOntologyManager man = OWLManager.createOWLOntologyManager();
	OWLOntology fromOntology;
	OWLOntology toOntology;
	OWLOntology referencingOntology;

	private void run(String inputOwlFileName, String outputOwlFileName, String uriFileName, List<String> commands)
			throws OWLOntologyCreationException, IOException, OWLOntologyStorageException {
		
		File inputFile = new File(inputOwlFileName);
		inputFile = inputFile.getCanonicalFile();
		
		File outputFile = new File(outputOwlFileName);
		outputFile = outputFile.getCanonicalFile();
		
		File uriFile = new File(uriFileName);
		uriFile = uriFile.getCanonicalFile();
		
		fromOntology = man.loadOntologyFromOntologyDocument(inputFile);

		// by default delete axioms.
		boolean copy = false;
		if (commands.contains("copy")) {
			copy = true;
		}

		// by default create new output file
		if (commands.contains("append")) {
			System.out.println("Appending to ontology file: " + outputOwlFileName);
			toOntology = man.loadOntologyFromOntologyDocument(new FileInputStream(outputFile));
			referencingOntology = man.loadOntologyFromOntologyDocument(new FileInputStream("referencing-"
					+ outputFile.getName()));
		} else {
			System.out.println("Creating new ontology in file: " + outputOwlFileName);
			toOntology = man.createOntology();
			referencingOntology = man.createOntology();
		}

		BufferedReader br = new BufferedReader(new FileReader(uriFile));
		String line = null;
		while ((line = br.readLine()) != null) {
			line = line.trim();
			// ignore blank lines.
			if (line.equals(""))
				continue;

			int i = line.indexOf(':');
			if (i == -1) {
				entitiesOnly.add(line.trim());
			} else {
				String option = line.substring(i + 1).trim().toLowerCase();
				if (option.equals("subs")) {
					subsOnly.add(line.substring(0, i).trim());
				} else if (option.equals("with-subs")) {
					entityWithSubs.add(line.substring(0, i).trim());
				}
			}
		}

		br.close();

		Set<OWLEntity> entities = new HashSet<OWLEntity>();

		for (String iriString : entitiesOnly) {
			IRI iri = IRI.create(iriString);
			entities.addAll(fromOntology.getEntitiesInSignature(iri, true));
		}

		for (String iriString : subsOnly) {
			IRI iri = IRI.create(iriString);
			Set<OWLEntity> subEntities = fromOntology.getEntitiesInSignature(iri, true);
			entities.addAll(getSubEntities(subEntities));
		}

		for (String iriString : entityWithSubs) {
			IRI iri = IRI.create(iriString);
			Set<OWLEntity> subsEntities = fromOntology.getEntitiesInSignature(iri, true);
			entities.addAll(subsEntities);
			entities.addAll(getSubEntities(subsEntities));
		}

		for (OWLEntity e : entities) {
			Set<OWLAxiom> entityAxioms = new HashSet<OWLAxiom>();
			Set<OWLAxiom> otherReferencingAxioms = new HashSet<OWLAxiom>();
			// add all referencing axioms.
			otherReferencingAxioms.addAll(e.getReferencingAxioms(fromOntology, true));
			for (OWLOntology o : fromOntology.getImportsClosure()) {
				if (e instanceof OWLClass) {
					OWLClass c = (OWLClass) e;
					// add defining logical axioms
					entityAxioms.addAll(o.getAxioms(c));
					// add annotation axioms
					entityAxioms.addAll(o.getAnnotationAssertionAxioms(c.getIRI()));
				} else if (e instanceof OWLObjectProperty) {
					OWLObjectProperty p = (OWLObjectProperty) e;
					entityAxioms.addAll(o.getAxioms(p));
				} else if (e instanceof OWLDataProperty) {
					OWLDataProperty p = (OWLDataProperty) e;
					entityAxioms.addAll(o.getAxioms(p));
				}

			}
			otherReferencingAxioms.removeAll(entityAxioms);

			man.addAxioms(toOntology, entityAxioms);
			man.addAxioms(referencingOntology, otherReferencingAxioms);

			// delete axioms if not a copy command
			if (!copy) {
				for (OWLOntology o : fromOntology.getImportsClosure()) {
					man.removeAxioms(o, entityAxioms);
					man.removeAxioms(o, otherReferencingAxioms);
				}
			}
		}

		man.saveOntology(fromOntology);
		man.saveOntology(toOntology, new FileOutputStream(outputFile));
		man.saveOntology(referencingOntology, new FileOutputStream(new File("referencing-"
				+ outputFile.getName())));
	}

	private Collection<? extends OWLEntity> getSubEntities(Set<OWLEntity> entities) {
		Set<OWLEntity> subEntities = new HashSet<OWLEntity>();
		for (OWLEntity e : entities) {
			if (e instanceof OWLClass) {
				OWLClass c = (OWLClass) e;
				getSubClasses(c, subEntities);
			} else if (e instanceof OWLProperty) {
				OWLProperty p = (OWLProperty) e;
				getSubProperties(p, subEntities);
			}
		}
		return subEntities;
	}

	Set<OWLProperty> seenProperties = new HashSet<OWLProperty>();

	private void getSubProperties(OWLProperty p, Set<OWLEntity> subEntities) {
		if (seenProperties.contains(p))
			return;
		seenProperties.add(p);
		for (Object subP : p.getSubProperties(fromOntology.getImportsClosure())) {
			if (subP instanceof OWLProperty) {
				subEntities.add((OWLProperty) subP);
				getSubProperties((OWLProperty) subP, subEntities);
			}
		}

	}

	Set<OWLClass> seenClasses = new HashSet<OWLClass>();

	private void getSubClasses(OWLClass c, Set<OWLEntity> subEntities) {
		if (seenClasses.contains(c))
			return;
		seenClasses.add(c);
		for (OWLClassExpression subC : c.getSubClasses(fromOntology.getImportsClosure())) {
			if (subC instanceof OWLClass) {
				subEntities.add((OWLClass) subC);
				getSubClasses((OWLClass) subC, subEntities);
			}
		}
	}

}
